<script setup lang="ts">
import { PerspectiveCamera, Scene } from "three";
import type { WebGLRendererParameters, ColorSpace, ShadowMapType, ToneMapping } from "three";
import type { Ref, App } from "vue";
import {
	computed,
	onMounted,
	provide,
	ref,
	shallowRef,
	watch,
	watchEffect,
	Fragment,
	defineComponent,
	h,
	getCurrentInstance,
} from "vue";
import {
	useContextProvider,
	useLogger,
	usePointerEventHandler,
	useRenderLoop,
	type Context,
} from "../compositions/index";
import { extend } from "../core/catalogue";
import { render } from "../core/renderer";

import { YJContext } from "../keys";

import type { TresCamera, TresObject } from "../types/";

export interface TresCanvasProps extends Omit<WebGLRendererParameters, "canvas"> {
	// required by for useRenderer
	shadows?: boolean;
	clearColor?: string;
	toneMapping?: ToneMapping;
	shadowMapType?: ShadowMapType;
	outputColorSpace?: ColorSpace;
	toneMappingExposure?: number;

	// required by useContextProvider
	camera?: TresCamera;
	preset?: string;
	windowSize?: boolean;
	disableRender?: boolean;
}

const props = withDefaults(defineProps<TresCanvasProps>(), {
	alpha: undefined,
	depth: undefined,
	shadows: undefined,
	stencil: undefined,
	antialias: undefined,
	windowSize: undefined,
	disableRender: undefined,
	preserveDrawingBuffer: undefined,
	logarithmicDepthBuffer: undefined,
	failIfMajorPerformanceCaveat: undefined,
});

const { logWarning } = useLogger();

const canvas = ref<HTMLCanvasElement>();
/*
 `scene` is defined here and not in `useContextProvider` because the custom
 renderer uses it to mount the app nodes. This happens before `useContextProvider` is called.
 The custom renderer requires `scene` to be editable (not readonly).
*/
const scene = shallowRef(new Scene());

const { resume } = useRenderLoop();

// 获得 TTCanvas 里面的内容
const slots = defineSlots<{
	default(): any;
}>();

const instance = getCurrentInstance()?.appContext.app;

const createInternalComponent = (context: Context) =>
	defineComponent({
		setup() {
			const ctx = getCurrentInstance()?.appContext;
			if (ctx) ctx.app = instance as App;
			provide(YJContext, context);
			provide("extend", extend);
			console.log(slots.default());
			return () => h(Fragment, null, slots.default() || []);
		},
	});

const mountCustomRenderer = (context: Context) => {
	const InternalComponent = createInternalComponent(context);
	render(h(InternalComponent), scene.value as unknown as TresObject);
};

const dispose = (context: Context) => {
	scene.value.children = [];
	mountCustomRenderer(context);
	resume();
};

const disableRender = computed(() => props.disableRender);

const context = shallowRef<Context | null>(null);

defineExpose({ context });

onMounted(() => {
	const existingCanvas = canvas as Ref<HTMLCanvasElement>;

	context.value = useContextProvider({
		scene: scene.value,
		canvas: existingCanvas,
		windowSize: props.windowSize,
		disableRender,
		rendererOptions: props,
	});

	usePointerEventHandler({ scene: scene.value, contextParts: context.value });

	const { registerCamera, camera, cameras, deregisterCamera } = context.value;
	mountCustomRenderer(context.value);

	const addDefaultCamera = () => {
		const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
		camera.position.set(3, 3, 3);
		camera.lookAt(0, 0, 0);
		registerCamera(camera);

		const unwatch = watchEffect(() => {
			if (cameras.value.length >= 2) {
				camera.removeFromParent();
				deregisterCamera(camera);
				unwatch?.();
			}
		});
	};

	watch(
		() => props.camera,
		(newCamera, oldCamera) => {
			if (newCamera) registerCamera(newCamera);
			if (oldCamera) {
				oldCamera.removeFromParent();
				deregisterCamera(oldCamera);
			}
		},
		{
			immediate: true,
		},
	);

	if (!camera.value) {
		logWarning(
			"No camera found. Creating a default perspective camera. " +
				"To have full control over a camera, please add one to the scene.",
		);
		addDefaultCamera();
	}

	if (import.meta.hot && context.value)
		import.meta.hot.on("vite:afterUpdate", () => dispose(context.value as Context));
});
</script>

<template>
	<canvas
		ref="canvas"
		:data-scene="scene.uuid"
		:class="$attrs.class"
		:style="{
      display: 'block',
      width: '100%',
      height: '100%',
      position: windowSize ? 'fixed' : 'relative',
      top: 0,
      left: 0,
      pointerEvents: 'auto',
      touchAction: 'none',
      ...$attrs.style as Object,
    }"
	/>
</template>
